/* This class handles the communications with the log files*/

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.io.Reader;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;

public class LogFileHandler {
	
	public List<LogFileEntry> logFileList;
	private String HMACencryptedLocalLogFile;
	private String encryptedLocalLogFile;
	private String decryptedLocalLogFile;
	private String encryptedServerLogFile;
	private String decryptedServerLogFile;
	private String aesKey, hmacKey;
    private AES aes;
    private HMAC hmac;
    private boolean lock;
	
	LogFileHandler () {
		this.logFileList = new ArrayList<LogFileEntry>();
		this.HMACencryptedLocalLogFile = "HMAC_en_local_log_file.txt";
		this.decryptedLocalLogFile = "de_local_log_file.txt";
		this.encryptedLocalLogFile = "en_local_log_file.txt";
		this.decryptedServerLogFile = "de_server_log_file.txt";
		this.encryptedServerLogFile = "en_server_log_file.txt";
		this.aesKey = "I like cookies";
		this.hmacKey = "I don't like cookies";
		
		this.aes = new AES(aesKey);
		this.hmac = new HMAC(hmacKey);
		this.lock = false;
	}
	
	public boolean loadLocalLogFile(InputStream in){
		
		//partno, seqno, username, ops, hmac, hash
		logFileList.clear();
		
        BufferedReader br = new BufferedReader(new InputStreamReader(in));
        String strLine = null ;
        String tokens[];

        try {
			while ((strLine = br.readLine()) != null) {
				tokens = strLine.split(" ");				
				logFileList.add(new LogFileEntry(Float.parseFloat(tokens[0]), Integer.parseInt(tokens[1]), tokens[2], Integer.parseInt(tokens[3]), hexToByte(tokens[4]), tokens[5].getBytes()));				
				System.out.println("0: " + tokens[0] + " 1: " + tokens[1] + " 2: " + tokens[2] + " 3: " + tokens[3] + " 4: " + hexToByte(tokens[4]) + " 5: " + tokens[5]);    		
			}			
        	
        } catch (NumberFormatException e1) {
			// TODO Auto-generated catch block
			e1.printStackTrace();
			return false;
		} catch (IOException e1) {
			// TODO Auto-generated catch block
			e1.printStackTrace();
			return false;
		}
        
	    try {
			in.close();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			return false;
		} 
	    
		return true;
	}

	public static byte[] hexToByte( String hexString){
	    int len = hexString.length();
	    byte[] ba = new byte[len / 2];
	    for (int i = 0; i < len; i += 2) {
	        ba[i/2] = (byte) ((Character.digit(hexString.charAt(i), 16) << 4) + Character.digit(hexString.charAt(i+1), 16));
	    }
	    return ba;
	}
	  
	public boolean loadServerLogFile(InputStream in){
		
		//partno, seqno, username, ops, hmac, hash

		logFileList.clear();
    
        BufferedReader br = new BufferedReader(new InputStreamReader(in));
        String strLine = null ;
        String tokens[];

        try {
        	while ((strLine = br.readLine()) != null) {
				tokens = strLine.split(" ");				
				logFileList.add(new LogFileEntry(Float.parseFloat(tokens[0]), Integer.parseInt(tokens[1]), tokens[2], Integer.parseInt(tokens[3]), hexToByte(tokens[4]), tokens[5].getBytes()));				
				System.out.println("0: " + tokens[0] + " 1: " + tokens[1] + " 2: " + tokens[2] + " 3: " + tokens[3] + " 4: " + hexToByte(tokens[4]) + " 5: " + tokens[5]);
         	}
    				
		} catch (NumberFormatException e1) {
			// TODO Auto-generated catch block
			e1.printStackTrace();
			return false;
		} catch (IOException e1) {
			// TODO Auto-generated catch block
			e1.printStackTrace();
			return false;
		}
    
		return true;
	}
	
	public boolean saveLogFile(String logFileType){
	
		logFileList = mergeSortFileList(logFileList);

		int BUFFER = 2048;
		
		if(logFileType.equals("local")){
			//put data memory into path
			BufferedWriter out;
			try {
				out = new BufferedWriter(new FileWriter(decryptedLocalLogFile));
			
				for (int i=0; i<logFileList.size(); i++){
					out.write(logFileList.get(i).returnEntry());
				}
				
				out.close();
			
			}catch (IOException e1) {
				// TODO Auto-generated catch block
				e1.printStackTrace();
				lock = false;
				return false;
			}
			
			try {
				aes.encrypt(new FileInputStream(decryptedLocalLogFile), new FileOutputStream(encryptedLocalLogFile));		
			} catch (FileNotFoundException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
				lock = false;
				return false;
			}
			
			//find the HMAC
			hmac.generateHashFile(encryptedLocalLogFile,HMACencryptedLocalLogFile);
			
			//delete the log files from local directory
			File en_file = new File(encryptedLocalLogFile);
			File d_file = new File(decryptedLocalLogFile);
			en_file.delete();
			d_file.delete();
			
			lock = false;
			return true;
		}
		
		else{ 
			if (logFileType.equals("server")){
				//put data memory into path
				BufferedWriter out;
				try {
					out = new BufferedWriter(new FileWriter(decryptedServerLogFile));
				
					for (int i=0; i<logFileList.size(); i++){			
						out.write(logFileList.get(i).returnEntry());
					}
					
					out.close();
				
				}catch (IOException e1) {
					// TODO Auto-generated catch block
					e1.printStackTrace();
					lock = false;
					return false;
				}
				
				try {
					aes.encrypt(new FileInputStream(decryptedServerLogFile), new FileOutputStream(encryptedServerLogFile));		
				} catch (FileNotFoundException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
					lock = false;
					return false;
				}
				
				//find the HMAC
				hmac.generateHashFile(encryptedServerLogFile,"server_log_file_L");
				lock = false;
				
				//delete the log files from local directory
				File en_file = new File(encryptedServerLogFile);
				File d_file = new File(decryptedServerLogFile);
				en_file.delete();
				d_file.delete();
				
				return true;
			}
			else
				lock = false;
				return false;
		}
	}
	
	public boolean localLogFileHMACCheck(){
		try {
			InputStream in = new FileInputStream(HMACencryptedLocalLogFile);
			byte[] HMAC1 = extractHMAC(in);
			byte[] HMAC2 = hmac.hash(HMACencryptedLocalLogFile, in);
			
			in.close();
			if (HMAC1 == null || HMAC2 == null)
				return false;
			
			StringBuffer sb1 = new StringBuffer();
		    for (int i = 0; i < HMAC1.length; i++) {
		    	sb1.append(Integer.toString((HMAC1[i] & 0xff) + 0x100, 16).substring(1));
		    }
		    
		    StringBuffer sb2 = new StringBuffer();
		    for (int i = 0; i < HMAC2.length; i++) {
		    	sb2.append(Integer.toString((HMAC2[i] & 0xff) + 0x100, 16).substring(1));
		    }		    
		    
			System.out.println("local: HERE ARE THE HMACS: " + sb1.toString() + " & " + sb2.toString());
			
			if(sb1.toString().contains("0000000000000000000000000000000000000000000000000000000")){
				System.out.println("HMAC matches. HMAC check passed.");
				return true;
			}
			if (sb1.toString().equals(sb2.toString())){
				System.out.println("HMAC matches. HMAC check passed.");
				return true;
			}
			else {
				System.out.println("HMAC do not match. HMAC check failed.");
				return false;
			}

		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

		return false;
	}
	
	public boolean serverLogFileHMACCheck(InputStream in){
		byte[] HMAC1 = extractHMAC(in);
		byte[] HMAC2 = hmac.hash("server_log_file_L", in);
		
		if (HMAC1 == null || HMAC2 == null)
			return false;
		
		StringBuffer sb1 = new StringBuffer();
		for (int i = 0; i < HMAC1.length; i++) {
			sb1.append(Integer.toString((HMAC1[i] & 0xff) + 0x100, 16).substring(1));
		}
		
		StringBuffer sb2 = new StringBuffer();
		for (int i = 0; i < HMAC2.length; i++) {
			sb2.append(Integer.toString((HMAC2[i] & 0xff) + 0x100, 16).substring(1));
		}		
		
		System.out.println("server: HERE ARE THE HMACS: " + sb1.toString() + " & " + sb2.toString());
		
		if(sb1.toString().contains("0000000000000000000000000000000000000000000000000000000")){
			System.out.println("HMAC matches. HMAC check passed.");
			return true;
		}
		
		if (sb1.toString().equals(sb2.toString())){
			System.out.println("HMAC matches. HMAC check passed.");
			return true;
		}
		else {
			System.out.println("HMAC do not match. HMAC check failed.");
			return false;
		}
	}
	
	public byte[] extractHMAC(InputStream in){
		
		//read in the first 64 characters of the file
		byte[] hash = new byte[32];
    	
		try {
			in.read(hash);
			return hash;
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			return null;
		}
	}

	public boolean decryptLocalLogFile(OutputStream out){
		//load log file and decrypt into another file
		try {
			aes.decrypt(new FileInputStream(HMACencryptedLocalLogFile), out);
			return true;
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			return false;
		}
	}
	
	public boolean decryptServerLogFile(OutputStream out){
		//load server file from server and decrypt into another file
		try {
			aes.decrypt(new FileInputStream("server_log_file_L"), out);
			return true;
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			return false;
		}

	}
	
	public int getListSize(){
		return logFileList.size();
	}
	
	public int updateLocalLogFile(LogFileHandler serverLogFile){
		serverLogFile.logFileList = mergeSortFileList(serverLogFile.logFileList);
		logFileList = mergeSortFileList(logFileList);

		int i=0;
		int j=0;
		int result = 0; //-1 means error, 0 means no change, 1 means updated
		
		while(i<serverLogFile.getListSize()){
			while(j<logFileList.size()){
				if((serverLogFile.logFileList.get(i).getPartNo()== logFileList.get(j).getPartNo())&&
				(serverLogFile.logFileList.get(i).getSeqNo() == logFileList.get(j).getSeqNo())){
					i++;
					j++;
				}
				else{
					if((serverLogFile.logFileList.get(i).getPartNo()== serverLogFile.logFileList.get(i-1).getPartNo())
						&& (logFileList.get(j).getPartNo()!= logFileList.get(j-1).getPartNo())){
						logFileList.add(j,serverLogFile.logFileList.get(i));
						result = 1;
						j++;
						i++;
					}
				}
			}
			if(i<serverLogFile.getListSize()){
				logFileList.add(serverLogFile.logFileList.get(i));
				result = 1;
				i++;		
			}
		}
					
		return result;
	}
	
	private List<LogFileEntry> mergeSortFileList(List<LogFileEntry> m){
		//logFileList.
		if (m.size() <= 1) return m;
		 
        int middle = m.size() / 2;
        List<LogFileEntry> left = m.subList(0, middle);
        List<LogFileEntry> right = m.subList(middle, m.size());
 
        right = mergeSortFileList(right);
        left = mergeSortFileList(left);
        List<LogFileEntry> result = merge(left, right);
 
        return result;
	}
	
	public List<LogFileEntry> merge(List<LogFileEntry> left, List<LogFileEntry> right) {
		List<LogFileEntry> result = new ArrayList<LogFileEntry>();
        Iterator<LogFileEntry> it1 = left.iterator();
        Iterator<LogFileEntry> it2 = right.iterator();
 
        LogFileEntry x = it1.next();
        LogFileEntry y = it2.next();
        
        while (true) {
            //change the direction of this comparison to change the direction of the sort
            if (x.returnEntry().compareTo(y.returnEntry()) <= 0) {
            	result.add(x);
		
				if (it1.hasNext())
				    x = it1.next();
				else {
				    result.add(y);
				    while (it2.hasNext())
					result.add(it2.next());
				    break;
				}
            } else {
				result.add(y);
				if (it2.hasNext())
				    y = it2.next();
				else {
				    result.add(x);
				    while (it1.hasNext())
					result.add(it1.next());
				    break;
				}
            }
        }
        return result;
    }
	
	public void addEntry(float partNo, int seqNo, String username, int ops, byte[] HMAC, byte[] hash){
		lock = true;
		//partno, seqno, username, ops, hmac, hash
		logFileList.add(new LogFileEntry(partNo, seqNo, username, ops, HMAC, hash));
	}
	
	public int getNextSeqNo(float partNo){
		int currentSeqNo = 0;
		int entrySeqNo = 0;
		
		for (int i = 0; i < logFileList.size(); i++) {
		    if(logFileList.get(i).getPartNo()==(partNo)){
		    	entrySeqNo = logFileList.get(i).getSeqNo();
		        if(entrySeqNo > currentSeqNo){
		        	currentSeqNo = entrySeqNo;
		        }
		    }
		}
		
		return currentSeqNo + 1;
	}
	
	//get the latest hash of the subfile part
	public byte[] getLatestHash(float partNo, int latestSeqNo){
		byte[] hash = new byte[16];
		
		for (int i = 0; i < logFileList.size(); i++) {
		    if(logFileList.get(i).getPartNo()==(partNo) && logFileList.get(i).getSeqNo()==latestSeqNo){
		    	hash = logFileList.get(i).getHash();
		    }
		}
		
		return hash;
	}
	
	//get the latest HMAC of the subfile part
	public byte[] getLatestHMAC(float partNo, int latestSeqNo){
		byte[] HMAC = new byte[32];
		
		for (int i = 0; i < logFileList.size(); i++) {
		    if(logFileList.get(i).getPartNo()==(partNo) && logFileList.get(i).getSeqNo()==latestSeqNo){
		    	HMAC = logFileList.get(i).getHMAC();
		    }
		}
		
		return HMAC;
	}
	
	public ArrayList<String> generateFileNameListFromLogFile(){
		logFileList = mergeSortFileList(logFileList);
		
		//generates list of part numbers
		ArrayList<String> fileNameList = new ArrayList<String>();
		if (logFileList.size() == 0)
			return null;
		for (int i = 0; i < logFileList.size(); i++) {
			if(i == logFileList.size()-1){
				fileNameList.add(Float.toString(logFileList.get(i).getPartNo()));
				break;
			}
				
			if(logFileList.get(i).getPartNo() != logFileList.get(i+1).getPartNo())
				if(logFileList.get(i).getOps()!= 2)
					fileNameList.add(Float.toString(logFileList.get(i).getPartNo()));				
		}
		
		return fileNameList;
	}
	
	public boolean getLock(){
		return lock;
	}
	
	public void main(String args[]){
		
	}
}
